package com.zeny.express.framework.core.database.masterslave;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;

import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.util.Assert;

public class MasterSlaveDataSource extends AbstractDataSource {

	private String clusterName;
	
	private DataSource masterDataSource;

	private Map<String, DataSource> slaveDataSources;

	/**
	 * @param clusterName
	 */
	public void setClusterName(String clusterName) {
		this.clusterName = clusterName;
	}

	/**
	 * @param masterDataSource
	 */
	public void setMasterDataSource(DataSource masterDataSource) {
		this.masterDataSource = masterDataSource;
	}

	/**
	 * @param slaveDataSource
	 */
	public void setSlaveDataSources(Map<String, DataSource> slaveDataSources) {
		this.slaveDataSources = slaveDataSources;
	}

	@PostConstruct
	public void init() {
		MasterSlaveCluster cluster = new MasterSlaveCluster();
		cluster.setClusterName(clusterName);
		cluster.setSlaves(new ArrayList<String>(slaveDataSources.keySet()));
		MasterSlaveContext.addCluster(cluster);
	}


	@Override
	public Connection getConnection() throws SQLException {
		return determineTargetDataSource().getConnection();
	}

	@Override
	public Connection getConnection(String username, String password) throws SQLException {
		return determineTargetDataSource().getConnection(username, password);
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T unwrap(Class<T> iface) throws SQLException {
		if (iface.isInstance(this)) {
			return (T) this;
		}
		return determineTargetDataSource().unwrap(iface);
	}

	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
	}

	/**
	 * Retrieve the current target DataSource. Determines the
	 * {@link #determineCurrentLookupKey() current lookup key}, performs
	 * a lookup in the {@link #setTargetDataSources slaveDataSource} map,
	 * falls back to the specified
	 * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
	 * @see #determineCurrentLookupKey()
	 */
	protected DataSource determineTargetDataSource() {
		Assert.notNull(this.slaveDataSources, "DataSource router not initialized");
		String lookupKey = determineCurrentLookupKey();
		DataSource dataSource = this.slaveDataSources.get(lookupKey);
		if (dataSource == null && lookupKey == null) {
			dataSource = this.masterDataSource;
		}
		if (dataSource == null) {
			throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
		}
		return dataSource;
	}

	protected String determineCurrentLookupKey() {
		
		return MasterSlaveContext.getDataSource(clusterName);
	}

}
